﻿/****************************************************************************
** file: PyramidTemplateMatch.h
** brief: 基于 OpenCV matchTemplete 实现的模板匹配算法，支持图像旋转匹配，采用图像金字塔技术加速。
** Copyright (C) LiYuan
** Author: LiYuan
** E-Mail: 18069211#qq(.)com
** Version 0.1
** Last modified: 2024.11.11
** Modified By: LiYuan
****************************************************************************/

#ifndef PYRAMIDTEMPLATEMATCH_H
#define PYRAMIDTEMPLATEMATCH_H

#include "RotateTemplateMatch.h"

namespace Qly {

class PyramidTemplateMatch;

/**
 * @brief The TemplateMatch2 class 内部类，只被 PyramidTemplateMatch 使用
 *         这个类是私有类，全部的方法和成员变量都是 private 类型的。
 *         只能被 PyramidTemplateMatch 使用。
 */
class RotateTemplateMatch2 : public RotateTemplateMatch
{
    friend PyramidTemplateMatch;
    /**
     * @brief regionMatch 只在 rect 范围内做模板匹配。如果 rect 为空，则在全 image 范围内查找
     * @param [in] image 输入的图像
     * @param [inout] rect ROI 区域，如果这个区域 width 或 height 为 0，则在全 image 范围内做模板匹配。
     *                     返回匹配的最佳区域
     * @param [inout] index
     *                 角度 index 值，只匹配 index ,index + 1 所对应的角度。
     *                 如果index == 0 则只匹配 index 和 index + 1
     *                 如果 index == angleCount - 1 则只匹配 index。
     *                 如果 index >= angleCount 则报错返回 false。
     *                 最后返回匹配到的最佳的位置和 index，根据 index 值可以计算出旋转角度。
     * @param score 返回匹配的分数。
     * @return
     */
    bool regionMatch(const cv::Mat&image, cv::Rect &rect, size_t& index, double &score);

    /**
     * @brief regionMatch 只在 rect 范围内做模板匹配。如果 rect 为空，则在全 image 范围内查找。 带亚像素精度插值。
     * @param image 输入的图像
     * @param rect ROI 区域，如果这个区域 width 或 height 为 0，则在全 image 范围内做模板匹配。
     * @param angle 角度值，只在这个角度的周边小范围内搜索
     * @param rrt 返回值，返回最佳的匹配位置和角度
     * @param score 匹配分数
     * @return
     */
    bool regionMatch(const cv::Mat&image, const cv::Rect &rect, double angle, cv::RotatedRect &rrt, double &score);
    /**
     * @brief roi 确定合适的 ROI 区域
     * @param image 待检测图像
     * @param index angleIndex， 表明在这个角度周边搜索
     * @param border 搜索区域应该四周比 model 大 border 个像素。
     * @param rect 上一次 regionMatch 确定的区域。这个区域的大小和 model 大小相同。返回扩大区域后的搜索范围
     * @return true 表示成功，false 表示 index 有错误。
     */
    bool roi(const cv::Mat &image, size_t  angleIndex, int border, cv::Rect &rect);
    bool roi(const cv::Mat &image, const cv::RotatedRect &rrt, int border, cv::Rect &rect);
    bool angle(size_t index, double &angle);
    double angle(size_t index);
    bool index(double angle, int &idx);
    cv::Mat m_result;
};


/**
 * @brief The PyramidTemplateMatch class 使用了图像金字塔的带旋转的模板匹配。为了方便快速二分查找，旋转的点数必须是 2^N。
 */
class PyramidTemplateMatch
{
public:
    PyramidTemplateMatch(int maxDepth = 4);
    /**
     * @brief setModelByAngleCount 生成模型数据，要求旋转的次数必须是 2^N。
     * @param model 模板图像，必须是单通道图像
     * @param angleMin 旋转角度起始值
     * @param angleMax 旋转角度终止值，[angleMin, angleMax] 就是搜索区域
     * @param k  实际的点数为 2^k
     * @return
     */
    bool setModelByAngleCount(const cv::Mat &model, double angleMin, double angleMax, size_t k);
 //   bool setModelByDetAngle(const cv::Mat &model, double angleMin, double angleMax, double detAngle);
    bool setModelByAngleCount(const cv::Mat &model, const cv::Mat &mask, double angleMin, double angleMax, size_t k);
//    bool setModelByDetAngle(const cv::Mat &model, const cv::Mat &mask, double angleMin, double angleMax, double detAngle); // 有可能调整angleMax
    void setMethod( int method = cv::TM_CCORR ) {m_matchMethod = method;}
    void setSizeMode(RotateTemplateMatch::MODEL_SIZE_MODE mode) {m_sizeMode = mode;}
    void setSubPixMode(bool enable) {m_enableSubPix = enable;}
    bool match0(const cv::Mat &image, cv::RotatedRect &rrt, double &score);
    bool match(const cv::Mat &image, cv::RotatedRect &rrt, double &score);
private:
    bool buildModels(const cv::Mat &model, const cv::Mat &mask);
    std::vector<RotateTemplateMatch2> m_pyMatch; // 模板图像金字塔
    int m_maxDepth;  // 最多降采样 m_maxDepth
    int m_minModelSize = 20; // 当 model 的长或宽小于 m_minModelSize 时就停止进一步做下一层的金字塔 model。
    int m_minAngleCount = 2;
    int m_depthCount = 0;

    cv::Mat m_model;
    cv::Mat m_mask;
    double m_angleMin;
    double m_angleMax;
    double m_angleDet;
    size_t m_angleCount;

    RotateTemplateMatch::MODEL_SIZE_MODE m_sizeMode = RotateTemplateMatch::MODEL_SIZE_MODE::FIX_MODEL_SIZE;
    int m_matchMethod = cv::TM_CCORR_NORMED;
    bool m_enableSubPix = false;

};

}

#endif // PYRAMIDTEMPLATEMATCH_H
